<div id="app"></div>
<script>
  // vdom
  function h(tag, props, children) {
    return {
      tag,
      props,
      children,
    };
  }

  function mount(vnode, container) {
    const tag = vnode.tag;
    const el = (vnode.el = document.createElement(tag)); // 在vnode上添加el属性，用来存储原dom结构

    // props
    if (vnode.props) {
      for (let key in vnode.props) {
        let value = vnode.props[key];
        // 判断属性传入的是以on开头的事件
        if (key.startsWith("on")) {
          el.addEventListener(key.slice(2).toLowerCase(), value);
        } else {
          el.setAttribute(key, value);
        }
      }
    }
    // children
    if (vnode.children) {
      if (typeof vnode.children === "string") {
        el.textContent = vnode.children;
      } else {
        vnode.children.forEach((child) => {
          mount(child, el);
        });
      }
    }

    container.appendChild(el);
  }
  // 将虚拟dom节点转为真实dom
  function createElm(vnode) {
    let { tag, props, children } = vnode;
    if (typeof tag === "string") {
      vnode.el = document.createElement(tag);
    }
    if (props) {
      for (let key in props) {
        let value = props[key];
        vnode.el.setAttribute(key, value);
      }
    }
    if (typeof vnode.children === "string") {
      vnode.el.textContent = vnode.children;
    } else {
      children.forEach((child) => {
        return vnode.el.appendChild(createElm(child));
      });
    }
    return vnode.el;
  }
  // dom diff过程
  function patch(oldVnode, newVnode) {
    if (oldVnode.tag === newVnode.tag) {
      const el = (newVnode.el = oldVnode.el); //把oldVnode的el真实dom内容，赋值给newVnode的el属性和单独的el对象上
      const oldProps = oldVnode.props || {};
      const newProps = newVnode.props || {};
      for (const key in newProps) {
        const oldValue = oldProps[key];
        const newValue = newProps[key];
        if (newValue !== oldValue) {
          el.setAttribute(key, newValue);
        }
      }
      // 处理新节点中不存在的属性,直接将属性移除
      for (const key in oldProps) {
        if (!(key in newProps)) {
          el.removeAttribute(key);
        }
      }
      const oldChildren = oldVnode.children;
      const newChildren = newVnode.children;
      if (typeof newChildren === "string") {
        // 新节点是字符串，直接删除旧节点，并使新接的文本
        if (typeof oldChildren === "string") {
          if (newChildren !== oldChildren) {
            el.textContent = newChildren;
          }
        } else {
          //旧节点不是字符串，说明包含多个子节点。同样也直接删除
          el.textContent = newChildren;
        }
      } else {
        //
        if (typeof oldChildren === "string") {
          //旧节点是字符串，新节点是多个子元素
          el.innerHTML = "";
          newChildren.forEach((child) => {
            mount(child, el);
          });
        } else {
          //旧节点多个子元素，新节点多个子元素
          // 找出新旧节点最长的共用长度
          const commonLength = Math.min(oldChildren.length, newChildren.length);
          // 比对公共长度的节点
          for (let i = 0; i < commonLength; i++) {
            patch(oldChildren[i], newChildren[i]);
          }
          // 如果新节点长度大于旧节点长度
          if (newChildren.length > oldChildren.length) {
            newChildren.slice(oldChildren.length).forEach((child) => {
              mount(child, el);
            });
          }
          // 如果旧节点长度大于新节点长度
          if (newChildren.length < oldChildren.length) {
            oldChildren.slice(newChildren).forEach((child) => {
              el.removeChild(child.el);
            });
          }
        }
      }
    } else {
      // 直接替换replace
      oldVnode.el.parentNode.replaceChild(createElm(vdom2), oldVnode.el);
    }
  }

  // reactive
  let activeEffect = null;
  class Dep {
    constructor(value) {
      this._value = value;
      this.effects = new Set(); //使用Set对象存放所有effect
    }
    get value() {
      this.track();
      return this._value;
    }
    set value(newValue) {
      if (newValue != this._value) {
        this._value = newValue;
      }
      this.trigger();
    }

    track() {
      if (activeEffect) {
        this.effects.add(activeEffect);
      }
    }
    trigger() {
      this.effects.forEach((effect) => {
        effect();
      });
    }
  }

  function watchEffect(effect) {
    activeEffect = effect;
    effect();
    activeEffect = null;
  }
  const targetMap = new WeakMap(); //创建全局Map存储被监测对象的所有属性的dep

  function findDep(target, key) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      depsMap = new Map(); // depsMap存储属性的dep
      targetMap.set(target, depsMap);
    }

    let dep = depsMap.get(key);
    if (!dep) {
      dep = new Dep();
      depsMap.set(key, dep); //建立被监测对象属性key和dep的关系
    }
    return dep;
  }
  const reactiveHandlers = {
    get(target, key, receiver) {
      const dep = findDep(target, key);
      dep.track();
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const dep = findDep(target, key);
      const res = Reflect.set(target, key, value, receiver);
      dep.trigger();
      return res;
    },
  };

  function reactive(raw) {
    return new Proxy(raw, reactiveHandlers);
  }
  // 创建app实例，内部调用了mount进行挂载，并调用patch进行dom diff
  function mountApp(component, container) {
    let isMounted = false;
    let preVnode = null; // 保存上次的vnode数据，用于下次的dom diff
    watchEffect(() => {
      if (!isMounted) {
        //第一次进入，还没完成挂载
        preVnode = component.render();
        mount(preVnode, container);
        isMounted = true;
      } else {
        // 之后属于 update
        const newVnode = component.render();
        patch(preVnode, newVnode);
        preVnode = newVnode;
      }
    });
  }
  // test Example
  const App = {
    data: reactive({
      count: 0,
    }),
    render() {
      return h(
        "div",
        {
          onClick: () => {
            this.data.count++;
          },
        },
        String(this.data.count)
      );
    },
  };

  mountApp(App, document.getElementById("app"));
</script>
